﻿// Copyright (c) 2012 - 2020 Jared Zheng <jaredz at outlook dot com>
// Buddha Framework is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of the Mulan PSL v2.
// You may obtain a copy of Mulan PSL v2 at:
//          http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
// EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
// MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.
//
// Create : 2012-12-01 version 0.1 Jared Zheng <jaredz at outlook dot com>
// Update : 2018-12-05 version 3.1.5 Jared Zheng <jaredz at outlook dot com>

#ifndef __REDIS_WRAPPER_INL__
#define __REDIS_WRAPPER_INL__

#pragma once

#ifdef USE_HIREDIS_LIB

///////////////////////////////////////////////////////////////////////////////////////////////////////////
// CRedisWrapper
INLINE CRedisWrapper::CRedisWrapper(void)
: m_pContext(nullptr)
, m_pLog(nullptr)
, m_nStatus(0)
, m_nPort(REDIS_PORT_DEFAULT)
, m_nIndex(0)
, m_nTimeout(REDIS_TIMEOUT_DEFAULT)
, m_nPipeCmd(0)
{
}

INLINE CRedisWrapper::~CRedisWrapper(void)
{
    Disconnect();
}

INLINE bool CRedisWrapper::Connect(CString& strAddr, CString& strPW, Int nPort, Int nIndex)
{
    return Connect(*strAddr, *strPW, nPort, nIndex);
}

INLINE bool CRedisWrapper::Connect(PCXStr pszAddr, PCXStr pszPW, Int nPort, Int nIndex)
{
    Disconnect();

    m_nIndex   = nIndex;
    m_nPort    = nPort;
    m_strPW    = pszPW;
    m_strAddr  = pszAddr;
    timeval tv = { 0 };
#if (__PLATFORM_TARGET__ == PLATFORM_TARGET_WINDOWS)
    tv.tv_sec  = (m_nTimeout / TIMET_XS2XS);
    tv.tv_usec = m_nTimeout - (tv.tv_sec * TIMET_XS2XS);
#endif
    m_pContext = redisConnectWithTimeout(pszAddr, nPort, tv);
    if ((m_pContext == nullptr) || (m_pContext->err != 0))
    {
        if (m_pContext == nullptr)
        {
            DEV_ERROR(TF("CRedisWrapper connect host=%s[%d] return nullptr"), pszAddr, nPort);
            if (m_pLog != nullptr)
            {
                LOGV_ERROR(*m_pLog, TF("CRedisWrapper connect host=%s[%d] return nullptr"), pszAddr, nPort);
            }
        }
        else
        {
            DEV_ERROR(TF("CRedisWrapper connect host=%s[%d] return error=[%d]%s"), pszAddr, nPort, m_pContext->err, m_pContext->errstr);
            if (m_pLog != nullptr)
            {
                LOGV_ERROR(*m_pLog, TF("CRedisWrapper connect host=%s[%d] return error=[%d]%s"), pszAddr, nPort, m_pContext->err, m_pContext->errstr);
            }
        }
        return false;
    }
    if ((pszPW != nullptr) && (*pszPW != 0))
    {
        redisReply* pReply = (redisReply*)redisCommand(m_pContext, TF("AUTH %s"), pszPW);
        if ((pReply == nullptr) || (pReply->type == REDIS_REPLY_ERROR))
        {
            DEV_ERROR(TF("CRedisWrapper connect host=%s[%d] AUTH return failed"), pszAddr, nPort);
            if (m_pLog != nullptr)
            {
                LOGV_ERROR(*m_pLog, TF("CRedisWrapper connect host=%s[%d] AUTH return failed"), pszAddr, nPort);
            }
            if (pReply != nullptr)
            {
                freeReplyObject(pReply);
            }
            Disconnect();
            return false;
        }
        freeReplyObject(pReply);
    }
    if (m_nIndex > 0)
    {
        redisReply* pReply = (redisReply*)redisCommand(m_pContext, TF("SELECT %d"), m_nIndex);
        if (pReply != nullptr)
        {
            if ((pReply->type == REDIS_REPLY_STATUS) && (CXChar::Cmp(pReply->str, TF("OK")) == 0))
            {
                DEV_DUMP(TF("CRedisWrapper select db=%d return okay"), m_nIndex);
                if (m_pLog != nullptr)
                {
                    LOGV_DUMP(*m_pLog, TF("CRedisWrapper select db=%d return okay"), m_nIndex);
                }
            }
            freeReplyObject(pReply);
        }
    }
    DEV_DUMP(TF("CRedisWrapper connect host=%s[%d] auth return okay"), pszAddr, nPort);
    if (m_pLog != nullptr)
    {
        LOGV_DUMP(*m_pLog, TF("CRedisWrapper connect host=%s[%d] auth return okay"), pszAddr, nPort);
    }
    return IsConnect();
}

INLINE void CRedisWrapper::Disconnect(void)
{
    if (m_pContext != nullptr)
    {
        redisFree(m_pContext);
        m_pContext = nullptr;
    }
    m_nPipeCmd = 0;
}

INLINE bool CRedisWrapper::IsConnect(void)
{
    return (m_pContext != nullptr);
}

INLINE bool CRedisWrapper::Ping(void)
{
    if (IsConnect())
    {
        redisReply* pReply = (redisReply*)redisCommand(m_pContext, TF("PING"));
        if ((pReply == nullptr) || (pReply->type != REDIS_REPLY_STATUS))
        {
            DEV_WARN(TF("CRedisWrapper ping host=%s[%d] return error, disconnect"), *m_strAddr, m_nPort);
            if (m_pLog != nullptr)
            {
                LOGV_WARN(*m_pLog, TF("CRedisWrapper ping host=%s[%d] return error, disconnect"), *m_strAddr, m_nPort);
            }
            if (pReply != nullptr)
            {
                freeReplyObject(pReply);
            }
            Disconnect();
            return false;
        }
        bool bRet = (CXChar::Cmp(pReply->str, TF("PONG")) == 0);
        freeReplyObject(pReply);

        return bRet;
    }
    return false;
}

INLINE bool CRedisWrapper::ReConnect(void)
{
    return Connect(m_strAddr, m_strPW, m_nPort, m_nIndex);
}

INLINE bool CRedisWrapper::CheckConnect(void)
{
    if (Ping() == false)
    {
        return ReConnect();
    }
    return true;
}

INLINE Int CRedisWrapper::GetPort(void)
{
    return m_nPort;
}

INLINE CString& CRedisWrapper::GetAddr(void)
{
    return m_strAddr;
}

INLINE CString& CRedisWrapper::GetPW(void)
{
    return m_strPW;
}

INLINE Int CRedisWrapper::GetTimeout(void)
{
    return m_nTimeout;
}

INLINE void CRedisWrapper::SetTimeout(Int nTimeout)
{
    m_nTimeout = nTimeout;
    DEF::Min<Int>(m_nTimeout, REDIS_TIMEOUT_MIN);
}

INLINE void CRedisWrapper::SetFileLog(CFileLog& Log)
{
    m_pLog = &Log;
}

INLINE CFileLog* CRedisWrapper::GetFileLog(void)
{
    return m_pLog;
}

INLINE Int CRedisWrapper::GetStatus(void)
{
    return m_nStatus;
}

INLINE void CRedisWrapper::SetStatus(Int nStatus)
{
    m_nStatus = nStatus;
}

INLINE bool CRedisWrapper::GetString(CString& strKey, CString& strValue)
{
    if (IsConnect() == false)
    {
        return false;
    }
    return GetString(*strKey, strKey.Length(), strValue);
}

INLINE bool CRedisWrapper::GetString(PCXStr pszKey, CString& strValue)
{
    if (IsConnect() == false)
    {
        return false;
    }
    return GetString(pszKey, CXChar::Length(pszKey), strValue);
}

INLINE bool CRedisWrapper::GetString(CString& strKey, PByte pData, size_t stSize)
{
    if (IsConnect() == false)
    {
        return false;
    }
    return GetString(*strKey, strKey.Length(), pData, stSize);
}

INLINE bool CRedisWrapper::GetString(PCXStr pszKey, PByte pData, size_t stSize)
{
    if (IsConnect() == false)
    {
        return false;
    }
    return GetString(pszKey, CXChar::Length(pszKey), pData, stSize);
}

INLINE bool CRedisWrapper::SetString(CString& strKey, CString& strValue, Int nSecond)
{
    if (IsConnect() == false)
    {
        return false;
    }
    return SetString(*strKey, strKey.Length(), *strValue, strValue.Length(), nSecond);
}

INLINE bool CRedisWrapper::SetString(CString& strKey, PCXStr pszValue, Int nSecond)
{
    if (IsConnect() == false)
    {
        return false;
    }
    return SetString(*strKey, strKey.Length(), pszValue, CXChar::Length(pszValue), nSecond);
}

INLINE bool CRedisWrapper::SetString(PCXStr pszKey, CString& strValue, Int nSecond)
{
    if (IsConnect() == false)
    {
        return false;
    }
    return SetString(pszKey, CXChar::Length(pszKey), *strValue, strValue.Length(), nSecond);
}

INLINE bool CRedisWrapper::SetString(PCXStr pszKey, PCXStr pszValue, Int nSecond)
{
    if (IsConnect() == false)
    {
        return false;
    }
    return SetString(pszKey, CXChar::Length(pszKey), pszValue, CXChar::Length(pszValue), nSecond);
}

INLINE bool CRedisWrapper::SetString(CString& strKey, PByte pData, size_t stSize, Int nSecond)
{
    if (IsConnect() == false)
    {
        return false;
    }
    return SetString(*strKey, strKey.Length(), pData, stSize, nSecond);
}

INLINE bool CRedisWrapper::SetString(PCXStr pszKey, PByte pData, size_t stSize, Int nSecond)
{
    if (IsConnect() == false)
    {
        return false;
    }
    return SetString(pszKey, CXChar::Length(pszKey), pData, stSize, nSecond);
}

INLINE Int CRedisWrapper::AddSetMember(CString& strKey, CString& strMember)
{
    if (IsConnect() == false)
    {
        return -1;
    }
    return AddSetMember(*strKey, strKey.Length(), *strMember, strMember.Length());
}

INLINE Int CRedisWrapper::AddSetMember(CString& strKey, PCXStr pszMember)
{
    if (IsConnect() == false)
    {
        return -1;
    }
    return AddSetMember(*strKey, strKey.Length(), pszMember, CXChar::Length(pszMember));
}

INLINE Int CRedisWrapper::AddSetMember(PCXStr pszKey, CString& strMember)
{
    if (IsConnect() == false)
    {
        return -1;
    }
    return AddSetMember(pszKey, CXChar::Length(pszKey), *strMember, strMember.Length());
}

INLINE Int CRedisWrapper::AddSetMember(PCXStr pszKey, PCXStr pszMember)
{
    if (IsConnect() == false)
    {
        return -1;
    }
    return AddSetMember(pszKey, CXChar::Length(pszKey), pszMember, CXChar::Length(pszMember));
}

INLINE Int CRedisWrapper::AddSetMember(CString& strKey, PByte pData, size_t stSize)
{
    if (IsConnect() == false)
    {
        return -1;
    }
    return AddSetMember(*strKey, strKey.Length(), (PCXStr)pData, stSize);
}

INLINE Int CRedisWrapper::AddSetMember(PCXStr pszKey, PByte pData, size_t stSize)
{
    if (IsConnect() == false)
    {
        return -1;
    }
    return AddSetMember(pszKey, CXChar::Length(pszKey), (PCXStr)pData, stSize);
}

INLINE Int CRedisWrapper::RemoveSetMember(CString& strKey, CString& strMember)
{
    if (IsConnect() == false)
    {
        return -1;
    }
    return RemoveSetMember(*strKey, strKey.Length(), *strMember, strMember.Length());
}

INLINE Int CRedisWrapper::RemoveSetMember(CString& strKey, PCXStr pszMember)
{
    if (IsConnect() == false)
    {
        return -1;
    }
    return RemoveSetMember(*strKey, strKey.Length(), pszMember, CXChar::Length(pszMember));
}

INLINE Int CRedisWrapper::RemoveSetMember(PCXStr pszKey, CString& strMember)
{
    if (IsConnect() == false)
    {
        return -1;
    }
    return RemoveSetMember(pszKey, CXChar::Length(pszKey), *strMember, strMember.Length());
}

INLINE Int CRedisWrapper::RemoveSetMember(PCXStr pszKey, PCXStr pszMember)
{
    if (IsConnect() == false)
    {
        return -1;
    }
    return RemoveSetMember(pszKey, CXChar::Length(pszKey), pszMember, CXChar::Length(pszMember));
}

INLINE Int CRedisWrapper::RemoveSetMember(CString& strKey, PByte pData, size_t stSize)
{
    if (IsConnect() == false)
    {
        return -1;
    }
    return RemoveSetMember(*strKey, strKey.Length(), (PCXStr)pData, stSize);
}

INLINE Int CRedisWrapper::RemoveSetMember(PCXStr pszKey, PByte pData, size_t stSize)
{
    if (IsConnect() == false)
    {
        return -1;
    }
    return RemoveSetMember(pszKey, CXChar::Length(pszKey), (PCXStr)pData, stSize);
}

INLINE Int CRedisWrapper::GetSetMemberCount(CString& strKey)
{
    if (IsConnect() == false)
    {
        return -1;
    }
    return GetSetMemberCount(*strKey, strKey.Length());
}

INLINE Int CRedisWrapper::GetSetMemberCount(PCXStr pszKey)
{
    if (IsConnect() == false)
    {
        return -1;
    }
    return GetSetMemberCount(pszKey, CXChar::Length(pszKey));
}

INLINE Int CRedisWrapper::GetSetMember(CString& strKey, CTArray<CString>& strMembers)
{
    if (IsConnect() == false)
    {
        return -1;
    }
    return GetSetMember(*strKey, strKey.Length(), strMembers);
}

INLINE Int CRedisWrapper::GetSetMember(PCXStr pszKey, CTArray<CString>& strMembers)
{
    if (IsConnect() == false)
    {
        return -1;
    }
    return GetSetMember(pszKey, CXChar::Length(pszKey), strMembers);
}

INLINE Int CRedisWrapper::GetSetMember(CString& strKey, CTArray<CBufReadStream>& strMembers)
{
    if (IsConnect() == false)
    {
        return -1;
    }
    return GetSetMember(*strKey, strKey.Length(), strMembers);
}

INLINE Int CRedisWrapper::GetSetMember(PCXStr pszKey, CTArray<CBufReadStream>& strMembers)
{
    if (IsConnect() == false)
    {
        return -1;
    }
    return GetSetMember(pszKey, CXChar::Length(pszKey), strMembers);
}

INLINE Int CRedisWrapper::GetSetMember(CString& strKey)
{
    if (IsConnect() == false)
    {
        return -1;
    }
    return GetSetMember(*strKey, strKey.Length());
}

INLINE Int CRedisWrapper::GetSetMember(PCXStr pszKey)
{
    if (IsConnect() == false)
    {
        return -1;
    }
    return GetSetMember(pszKey, CXChar::Length(pszKey));
}

INLINE bool CRedisWrapper::RemoveKey(CString& strKey)
{
    if (IsConnect() == false)
    {
        return false;
    }
    return RemoveKey(*strKey, strKey.Length());
}

INLINE bool CRedisWrapper::RemoveKey(PCXStr pszKey)
{
    if (IsConnect() == false)
    {
        return false;
    }
    return RemoveKey(pszKey, CXChar::Length(pszKey));
}

INLINE bool CRedisWrapper::ExistKey(CString& strKey)
{
    if (IsConnect() == false)
    {
        return false;
    }
    return ExistKey(*strKey, strKey.Length());
}

INLINE bool CRedisWrapper::ExistKey(PCXStr pszKey)
{
    if (IsConnect() == false)
    {
        return false;
    }
    return ExistKey(pszKey, CXChar::Length(pszKey));
}

INLINE Int CRedisWrapper::Publish(CString& strChannel, CString& strMsg)
{
    if (IsConnect() == false)
    {
        return -1;
    }
    return Publish(*strChannel, strChannel.Length(), *strMsg, strMsg.Length());
}

INLINE Int CRedisWrapper::Publish(CString& strChannel, PCXStr pszMsg)
{
    if (IsConnect() == false)
    {
        return -1;
    }
    return Publish(*strChannel, strChannel.Length(), pszMsg, CXChar::Length(pszMsg));
}

INLINE Int CRedisWrapper::Publish(PCXStr pszChannel, CString& strMsg)
{
    if (IsConnect() == false)
    {
        return -1;
    }
    return Publish(pszChannel, CXChar::Length(pszChannel), *strMsg, strMsg.Length());
}

INLINE Int CRedisWrapper::Publish(PCXStr pszChannel, PCXStr pszMsg)
{
    if (IsConnect() == false)
    {
        return -1;
    }
    return Publish(pszChannel, CXChar::Length(pszChannel), pszMsg, CXChar::Length(pszMsg));
}

INLINE bool CRedisWrapper::Subscribe(CString& strChannel)
{
    if (IsConnect() == false)
    {
        return false;
    }
    return Subscribe(*strChannel, strChannel.Length());
}

INLINE bool CRedisWrapper::Subscribe(PCXStr pszChannel)
{
    if (IsConnect() == false)
    {
        return false;
    }
    return Subscribe(pszChannel, CXChar::Length(pszChannel));
}

INLINE bool CRedisWrapper::Subscribe(CTArray<CString>& strChannels)
{
    if (IsConnect() == false)
    {
        return false;
    }
    return Subscribe(strChannels.GetSize(), strChannels);
}

INLINE bool CRedisWrapper::WaitForSubscribeMsg(CString& strChannel, CString& strMsg)
{
    if (IsConnect() == false)
    {
        return false;
    }
    bool bRet = false;
    redisReply* pReply = nullptr;
    if (redisGetReply(m_pContext, (void**)&pReply) == REDIS_OK)
    {
        if ((pReply->type == REDIS_REPLY_ARRAY) &&
            (pReply->elements >= 3))
        {
            if (CXChar::Cmp(pReply->element[0]->str, TF("message")) == 0)
            {
                strChannel = pReply->element[1]->str;
                strMsg = pReply->element[2]->str;
            }
            bRet = true;
        }
        freeReplyObject(pReply);
    }
    return bRet;
}

INLINE bool CRedisWrapper::IsPipeCmd(void)
{
    if (IsConnect() == false)
    {
        return false;
    }
    return (m_nPipeCmd > 0);
}

INLINE bool CRedisWrapper::BeginPipeCmd(void)
{
    if (IsConnect() == false)
    {
        return false;
    }
    assert(m_nPipeCmd == 0);
    m_nPipeCmd = 1;
    return true;
}

INLINE Int CRedisWrapper::EndPipeCmd(bool bAll)
{
    if (IsConnect() == false)
    {
        return -1;
    }

    if (m_nPipeCmd > 1)
    {
        bool        bRet = false;
        redisReply* pReply = nullptr;
        do
        {
            bRet = false;
            if (redisGetReply(m_pContext, (void**)&pReply) == REDIS_OK)
            {
                if ((pReply->type == REDIS_REPLY_STATUS) &&
                    (CXChar::Cmp(pReply->str, TF("OK")) == 0))
                {
                    bRet = true;
                }
                else if (pReply->type != REDIS_REPLY_ERROR)
                {
                    bRet = true;
                }
                else
                {
                    DEV_WARN(TF("CRedisWrapper EndPipeCmd return error=[%d]%s"), pReply->type, pReply->str);
                    if (m_pLog != nullptr)
                    {
                        LOGV_WARN(*m_pLog, TF("CRedisWrapper EndPipeCmd return error=[%d]%s"), pReply->type, pReply->str);
                    }
                }
                freeReplyObject(pReply);
                if (bRet)
                {
                    --m_nPipeCmd;
                    if (m_nPipeCmd > 1)
                    {
                        continue;
                    }
                    m_nPipeCmd = 0;
                    break;
                }
            }
            m_nPipeCmd = -1;
            break;
        } while (bAll);
    }
    if (m_nPipeCmd == 1)
    {
        m_nPipeCmd = 0;
    }
    else if (m_nPipeCmd == -1)
    {
        DEV_WARN(TF("CRedisWrapper endpipecmd host=%s[%d] return error, disconnect"), *m_strAddr, m_nPort);
        if (m_pLog != nullptr)
        {
            LOGV_WARN(*m_pLog, TF("CRedisWrapper endpipecmd host=%s[%d] return error, disconnect"), *m_strAddr, m_nPort);
        }
        Disconnect();
    }
    return m_nPipeCmd;
}

INLINE Int CRedisWrapper::EndPipeCmdReplay(CString& strValue)
{
    strValue.Empty();
    if (IsConnect() == false)
    {
        return -1;
    }
    while (m_nPipeCmd > 1)
    {
        bool        bRet   = false;
        redisReply* pReply = nullptr;
        if (redisGetReply(m_pContext, (void**)&pReply) == REDIS_OK)
        {
            if (pReply->type == REDIS_REPLY_STRING)
            {
                strValue = pReply->str;
                bRet = true;
            }
            else if (pReply->type != REDIS_REPLY_ERROR)
            {
                bRet = true;
            }
            else
            {
                DEV_WARN(TF("CRedisWrapper EndPipeCmdReplay1 return error=[%d]%s"), pReply->type, pReply->str);
                if (m_pLog != nullptr)
                {
                    LOGV_WARN(*m_pLog, TF("CRedisWrapper EndPipeCmdReplay1 return error=[%d]%s"), pReply->type, pReply->str);
                }
            }
            freeReplyObject(pReply);
            if (bRet)
            {
                --m_nPipeCmd;
                if (m_nPipeCmd == 1)
                {
                    m_nPipeCmd = 0;
                }
                break;
            }
        }
        m_nPipeCmd = -1;
    }
    if (m_nPipeCmd == -1)
    {
        DEV_WARN(TF("CRedisWrapper endpipecmd(string) host=%s[%d] return error, disconnect"), *m_strAddr, m_nPort);
        if (m_pLog != nullptr)
        {
            LOGV_WARN(*m_pLog, TF("CRedisWrapper endpipecmd(string) host=%s[%d] return error, disconnect"), *m_strAddr, m_nPort);
        }
        Disconnect();
    }
    return m_nPipeCmd;
}

INLINE Int CRedisWrapper::EndPipeCmdReplay(PByte pData, size_t& stSize)
{
    if (IsConnect() == false)
    {
        return -1;
    }
    while (m_nPipeCmd > 1)
    {
        bool        bRet = false;
        redisReply* pReply = nullptr;
        if (redisGetReply(m_pContext, (void**)&pReply) == REDIS_OK)
        {
            if (pReply->type == REDIS_REPLY_STRING)
            {
                MM_SAFE::Cpy(pData, stSize, pReply->str, (pReply->len < stSize) ? pReply->len : stSize);
                bRet = true;
            }
            else if (pReply->type != REDIS_REPLY_ERROR)
            {
                stSize = 0;
                bRet = true;
            }
            else
            {
                DEV_WARN(TF("CRedisWrapper EndPipeCmdReplay2 return error=[%d]%s"), pReply->type, pReply->str);
                if (m_pLog != nullptr)
                {
                    LOGV_WARN(*m_pLog, TF("CRedisWrapper EndPipeCmdReplay2 return error=[%d]%s"), pReply->type, pReply->str);
                }
            }
            freeReplyObject(pReply);
            if (bRet)
            {
                --m_nPipeCmd;
                if (m_nPipeCmd == 1)
                {
                    m_nPipeCmd = 0;
                }
                break;
            }
        }
        m_nPipeCmd = -1;
    }
    if (m_nPipeCmd == -1)
    {
        DEV_WARN(TF("CRedisWrapper endpipecmd(PByte) host=%s[%d] return error, disconnect"), *m_strAddr, m_nPort);
        if (m_pLog != nullptr)
        {
            LOGV_WARN(*m_pLog, TF("CRedisWrapper endpipecmd(PByte) host=%s[%d] return error, disconnect"), *m_strAddr, m_nPort);
        }
        Disconnect();
    }
    return m_nPipeCmd;
}

INLINE Int CRedisWrapper::EndPipeCmdReplay(Int& nValue)
{
    nValue = 0;
    if (IsConnect() == false)
    {
        return -1;
    }
    while (m_nPipeCmd > 1)
    {
        bool        bRet = false;
        redisReply* pReply = nullptr;
        if (redisGetReply(m_pContext, (void**)&pReply) == REDIS_OK)
        {
            if (pReply->type == REDIS_REPLY_INTEGER)
            {
                nValue = (Int)pReply->integer;
                bRet = true;
            }
            else if (pReply->type != REDIS_REPLY_ERROR)
            {
                bRet = true;
            }
            else
            {
                DEV_WARN(TF("CRedisWrapper EndPipeCmdReplay3 return error=[%d]%s"), pReply->type, pReply->str);
                if (m_pLog != nullptr)
                {
                    LOGV_WARN(*m_pLog, TF("CRedisWrapper EndPipeCmdReplay3 return error=[%d]%s"), pReply->type, pReply->str);
                }
            }
            freeReplyObject(pReply);
            if (bRet)
            {
                --m_nPipeCmd;
                if (m_nPipeCmd == 1)
                {
                    m_nPipeCmd = 0;
                }
                break;
            }
        }
        m_nPipeCmd = -1;
    }
    if (m_nPipeCmd == -1)
    {
        DEV_WARN(TF("CRedisWrapper endpipecmd(int) host=%s[%d] return error, disconnect"), *m_strAddr, m_nPort);
        if (m_pLog != nullptr)
        {
            LOGV_WARN(*m_pLog, TF("CRedisWrapper endpipecmd(int) host=%s[%d] return error, disconnect"), *m_strAddr, m_nPort);
        }
        Disconnect();
    }
    return m_nPipeCmd;
}

INLINE Int CRedisWrapper::EndPipeCmdReplay(CTArray<CString>& strValues)
{
    strValues.RemoveAll();
    if (IsConnect() == false)
    {
        return -1;
    }
    while (m_nPipeCmd > 1)
    {
        bool        bRet = false;
        redisReply* pReply = nullptr;
        if (redisGetReply(m_pContext, (void**)&pReply) == REDIS_OK)
        {
            if (pReply->type == REDIS_REPLY_ARRAY)
            {
                for (size_t i = 0; i < pReply->elements; ++i)
                {
                    if ((pReply->element[i]->type == REDIS_REPLY_STRING) &&
                        (pReply->element[i]->len > 0))
                    {
                        strValues.Add(pReply->element[i]->str);
                    }
                    else
                    {
                        strValues.Add(TF(""));
                    }
                }
                bRet = true;
            }
            else if (pReply->type != REDIS_REPLY_ERROR)
            {
                bRet = true;
            }
            else
            {
                DEV_WARN(TF("CRedisWrapper EndPipeCmdReplay4 return error=[%d]%s"), pReply->type, pReply->str);
                if (m_pLog != nullptr)
                {
                    LOGV_WARN(*m_pLog, TF("CRedisWrapper EndPipeCmdReplay4 return error=[%d]%s"), pReply->type, pReply->str);
                }
            }
            freeReplyObject(pReply);
            if (bRet)
            {
                --m_nPipeCmd;
                if (m_nPipeCmd == 1)
                {
                    m_nPipeCmd = 0;
                }
                break;
            }
        }
        m_nPipeCmd = -1;
    }
    if (m_nPipeCmd == -1)
    {
        DEV_WARN(TF("CRedisWrapper endpipecmd(string array) host=%s[%d] return error, disconnect"), *m_strAddr, m_nPort);
        if (m_pLog != nullptr)
        {
            LOGV_WARN(*m_pLog, TF("CRedisWrapper endpipecmd(string array) host=%s[%d] return error, disconnect"), *m_strAddr, m_nPort);
        }
        Disconnect();
    }
    return m_nPipeCmd;
}

INLINE bool CRedisWrapper::GetString(PCXStr pszKey, size_t stKey, CString& strValue)
{
    PCXStr pszArg[2];
    size_t stArg[2];

    pszArg[0] = "GET";
    stArg[0]  = 3;

    pszArg[1] = pszKey;
    stArg[1]  = stKey;

    if (IsPipeCmd() == false)
    {
        return Command(strValue, 2, pszArg, stArg);
    }
    else
    {
        return AppendCommand(2, pszArg, stArg);
    }
}

INLINE bool CRedisWrapper::GetString(PCXStr pszKey, size_t stKey, PByte pData, size_t stSize)
{
    PCXStr pszArg[2];
    size_t stArg[2];

    pszArg[0] = "GET";
    stArg[0]  = 3;

    pszArg[1] = pszKey;
    stArg[1]  = stKey;

    if (IsPipeCmd() == false)
    {
        return Command(pData, stSize, 2, pszArg, stArg);
    }
    else
    {
        return AppendCommand(2, pszArg, stArg);
    }
}

INLINE bool CRedisWrapper::SetString(PCXStr pszKey, size_t stKey, PCXStr pszValue, size_t stValue, Int nSecond)
{
    CString strSecond;
    strSecond.ToString(nSecond);

    Int    nArg = 0;
    PCXStr pszArg[4];
    size_t stArg[4];

    if (nSecond > 0)
    {
        nArg      = 3;
        pszArg[0] = "SETEX";
        stArg[0]  = 5;
        pszArg[1] = pszKey;
        stArg[1]  = stKey;
        pszArg[2] = *strSecond;
        stArg[2]  = strSecond.Length();
    }
    else
    {
        nArg      = 2;
        pszArg[0] = "SET";
        stArg[0]  = 3;
        pszArg[1] = pszKey;
        stArg[1]  = stKey;
    }
    pszArg[nArg]  = pszValue;
    stArg[nArg]   = stValue;
    ++nArg;

    if (IsPipeCmd() == false)
    {
        return Command(nArg, pszArg, stArg);
    }
    else
    {
        return AppendCommand(nArg, pszArg, stArg);
    }
}

INLINE bool CRedisWrapper::SetString(PCXStr pszKey, size_t stKey, PByte pData, size_t stSize, Int nSecond)
{
    CString strSecond;
    strSecond.ToString(nSecond);

    Int    nArg = 0;
    PCXStr pszArg[4];
    size_t stArg[4];

    if (nSecond > 0)
    {
        nArg      = 3;
        pszArg[0] = "SETEX";
        stArg[0]  = 5;
        pszArg[1] = pszKey;
        stArg[1]  = stKey;
        pszArg[2] = *strSecond;
        stArg[2]  = strSecond.Length();
    }
    else
    {
        nArg = 2;
        pszArg[0] = "SET";
        stArg[0]  = 3;
        pszArg[1] = pszKey;
        stArg[1]  = stKey;
    }
    pszArg[nArg]  = (PCXStr)pData;
    stArg[nArg]   = stSize;
    ++nArg;

    if (IsPipeCmd() == false)
    {
        return Command(nArg, pszArg, stArg);
    }
    else
    {
        return AppendCommand(nArg, pszArg, stArg);
    }
}

INLINE Int CRedisWrapper::AddSetMember(PCXStr pszKey, size_t stKey, PCXStr pszMember, size_t stMember)
{
    PCXStr pszArg[3];
    size_t stArg[3];

    pszArg[0] = "SADD";
    stArg[0]  = 4;

    pszArg[1] = pszKey;
    stArg[1]  = stKey;

    pszArg[2] = pszMember;
    stArg[2]  = stMember;

    if (IsPipeCmd() == false)
    {
        return CommandInt(3, pszArg, stArg);
    }
    else
    {
        return (AppendCommand(3, pszArg, stArg) == true) ? 0 : -1;
    }
}

INLINE Int CRedisWrapper::RemoveSetMember(PCXStr pszKey, size_t stKey, PCXStr pszMember, size_t stMember)
{
    PCXStr pszArg[3];
    size_t stArg[3];

    pszArg[0] = "SREM";
    stArg[0]  = 4;

    pszArg[1] = pszKey;
    stArg[1]  = stKey;

    pszArg[2] = pszMember;
    stArg[2]  = stMember;

    if (IsPipeCmd() == false)
    {
        return CommandInt(3, pszArg, stArg);
    }
    else
    {
        return (AppendCommand(3, pszArg, stArg) == true) ? 0 : -1;
    }
}

INLINE Int CRedisWrapper::GetSetMemberCount(PCXStr pszKey, size_t stKey)
{
    PCXStr pszArg[2];
    size_t stArg[2];

    pszArg[0] = "SCARD";
    stArg[0]  = 5;

    pszArg[1] = pszKey;
    stArg[1]  = stKey;

    if (IsPipeCmd() == false)
    {
        return CommandInt(2, pszArg, stArg);
    }
    else
    {
        return (AppendCommand(2, pszArg, stArg) == true) ? 0 : -1;
    }
}

INLINE Int CRedisWrapper::GetSetMember(PCXStr pszKey, size_t stKey, CTArray<CString>& strMembers)
{
    if (IsPipeCmd() == false)
    {
        PCXStr pszArg[2];
        size_t stArg[2];

        pszArg[0] = "SMEMBERS";
        stArg[0]  = 8;

        pszArg[1] = pszKey;
        stArg[1]  = stKey;

        return CommandInt(strMembers, 2, pszArg, stArg);
    }
    return -1;
}

INLINE Int CRedisWrapper::GetSetMember(PCXStr pszKey, size_t stKey, CTArray<CBufReadStream>& strMembers)
{
    if (IsPipeCmd() == false)
    {
        PCXStr pszArg[2];
        size_t stArg[2];

        pszArg[0] = "SMEMBERS";
        stArg[0]  = 8;

        pszArg[1] = pszKey;
        stArg[1]  = stKey;

        return CommandInt(strMembers, 2, pszArg, stArg);
    }
    return -1;
}

INLINE Int CRedisWrapper::GetSetMember(PCXStr pszKey, size_t stKey)
{
    if (IsPipeCmd())
    {
        PCXStr pszArg[2];
        size_t stArg[2];

        pszArg[0] = "SMEMBERS";
        stArg[0]  = 8;

        pszArg[1] = pszKey;
        stArg[1]  = stKey;

        return (AppendCommand(2, pszArg, stArg) == true) ? 0 : -1;
    }
    return -1;
}

INLINE bool CRedisWrapper::RemoveKey(PCXStr pszKey, size_t stKey)
{
    PCXStr pszArg[2];
    size_t stArg[2];

    pszArg[0] = "DEL";
    stArg[0]  = 3;

    pszArg[1] = pszKey;
    stArg[1]  = stKey;

    if (IsPipeCmd() == false)
    {
        return (CommandInt(2, pszArg, stArg) >= 0);
    }
    else
    {
        return AppendCommand(2, pszArg, stArg);
    }
}

INLINE bool CRedisWrapper::ExistKey(PCXStr pszKey, size_t stKey)
{
    if (IsPipeCmd() == false)
    {
        PCXStr pszArg[2];
        size_t stArg[2];

        pszArg[0] = "EXISTS";
        stArg[0]  = 6;

        pszArg[1] = pszKey;
        stArg[1]  = stKey;

        return (CommandInt(2, pszArg, stArg) > 0);
    }
    return false;
}

INLINE Int CRedisWrapper::Publish(PCXStr pszChannel, size_t stChannel, PCXStr pszMsg, size_t stMsg)
{
    if (IsPipeCmd() == false)
    {
        PCXStr pszArg[3];
        size_t stArg[3];

        pszArg[0] = "PUBLISH";
        stArg[0]  = 7;

        pszArg[1] = pszChannel;
        stArg[1]  = stChannel;

        pszArg[2] = pszMsg;
        stArg[2]  = stMsg;

        return CommandInt(3, pszArg, stArg);
    }
    return -1;
}

INLINE bool CRedisWrapper::Subscribe(PCXStr pszChannel, size_t stChannel)
{
    PCXStr pszArg[2];
    size_t stArg[2];

    pszArg[0] = "SUBSCRIBE";
    stArg[0]  = 9;

    pszArg[1] = pszChannel;
    stArg[1]  = stChannel;

    if (IsPipeCmd() == false)
    {
        return Command(2, pszArg, stArg);
    }
    else
    {
        return AppendCommand(2, pszArg, stArg);
    }
}

INLINE bool CRedisWrapper::Subscribe(Int nSize, CTArray<CString>& strChannels)
{
    Int nArg = nSize + 1;
    CBufReadStream brs(nArg * (sizeof(PCXStr) + sizeof(size_t)));
    PCXStr* ppszArg = (PCXStr*)brs.GetBuf();
    size_t* pstArg  = (size_t*)(brs.GetBuf() + nArg * sizeof(PCXStr));

    ppszArg[0] = "SUBSCRIBE";
    pstArg[0]  = 9;

    for (Int i = 0; i < nSize; ++i)
    {
        ppszArg[i + 1] = strChannels[i].GetBuffer();
        pstArg[i + 1]  = strChannels[i].Length();
    }

    if (IsPipeCmd() == false)
    {
        return Command(nArg, ppszArg, pstArg);
    }
    else
    {
        return AppendCommand(nArg, ppszArg, pstArg);
    }
}

INLINE bool CRedisWrapper::Command(Int nArg, PCXStr* pszArg, size_t* pstArg)
{
    Int nRet = REDIS_ERR;
    redisReply* pReply = (redisReply*)redisCommandArgv(m_pContext, nArg, pszArg, pstArg);
    if (pReply != nullptr)
    {
        if ((pReply->type == REDIS_REPLY_STATUS) &&
            (CXChar::Cmp(pReply->str, TF("OK")) == 0))
        {
            nRet = REDIS_OK;
        }
        else if (pReply->type != REDIS_REPLY_ERROR)
        {
            nRet = REDIS_ERR_OTHER;
        }
        else
        {
            DEV_WARN(TF("CRedisWrapper Command return error=[%d]%s"), pReply->type, pReply->str);
            if (m_pLog != nullptr)
            {
                LOGV_WARN(*m_pLog, TF("CRedisWrapper Command return error=[%d]%s"), pReply->type, pReply->str);
            }
        }
        freeReplyObject(pReply);
    }
    if (nRet == REDIS_ERR)
    {
        DEV_WARN(TF("CRedisWrapper command host=%s[%d] %s --- %s return false, disconnect"), *m_strAddr, m_nPort, pszArg[0], pszArg[1]);
        if (m_pLog != nullptr)
        {
            LOGV_WARN(*m_pLog, TF("CRedisWrapper command host=%s[%d] %s --- %s return false, disconnect"), *m_strAddr, m_nPort, pszArg[0], pszArg[1]);
        }
        Disconnect();
    }
    return (nRet == REDIS_OK);
}

INLINE bool CRedisWrapper::Command(CString& strValue, Int nArg, PCXStr* pszArg, size_t* pstArg)
{
    strValue.Empty();

    Int nRet = REDIS_ERR;
    redisReply* pReply = (redisReply*)redisCommandArgv(m_pContext, nArg, pszArg, pstArg);
    if (pReply != nullptr)
    {
        if (pReply->type == REDIS_REPLY_STRING)
        {
            strValue = pReply->str;
            nRet = REDIS_OK;
        }
        else if (pReply->type != REDIS_REPLY_ERROR)
        {
            nRet = REDIS_ERR_OTHER;
        }
        else
        {
            DEV_WARN(TF("CRedisWrapper Command1 return error=[%d]%s"), pReply->type, pReply->str);
            if (m_pLog != nullptr)
            {
                LOGV_WARN(*m_pLog, TF("CRedisWrapper Command1 return error=[%d]%s"), pReply->type, pReply->str);
            }
        }
        freeReplyObject(pReply);
    }
    if (nRet == REDIS_ERR)
    {
        DEV_WARN(TF("CRedisWrapper command1 host=%s[%d] %s --- %s return false, disconnect"), *m_strAddr, m_nPort, pszArg[0], pszArg[1]);
        if (m_pLog != nullptr)
        {
            LOGV_WARN(*m_pLog, TF("CRedisWrapper command1 host=%s[%d] %s --- %s return false, disconnect"), *m_strAddr, m_nPort, pszArg[0], pszArg[1]);
        }
        Disconnect();
    }
    return (strValue.Length() > 0);
}

INLINE bool CRedisWrapper::Command(PByte pData, size_t stSize, Int nArg, PCXStr* pszArg, size_t* pstArg)
{
    Int nRet = REDIS_ERR;
    redisReply* pReply = (redisReply*)redisCommandArgv(m_pContext, nArg, pszArg, pstArg);
    if (pReply != nullptr)
    {
        if (pReply->type == REDIS_REPLY_STRING)
        {
            MM_SAFE::Cpy(pData, stSize, pReply->str, (pReply->len < stSize) ? pReply->len : stSize);
            nRet = REDIS_OK;
        }
        else if (pReply->type != REDIS_REPLY_ERROR)
        {
            nRet = REDIS_ERR_OTHER;
        }
        else
        {
            DEV_WARN(TF("CRedisWrapper Command2 return error=[%d]%s"), pReply->type, pReply->str);
            if (m_pLog != nullptr)
            {
                LOGV_WARN(*m_pLog, TF("CRedisWrapper Command2 return error=[%d]%s"), pReply->type, pReply->str);
            }
        }
        freeReplyObject(pReply);
    }
    if (nRet == REDIS_ERR)
    {
        DEV_WARN(TF("CRedisWrapper command2 host=%s[%d] %s --- %s return false, disconnect"), *m_strAddr, m_nPort, pszArg[0], pszArg[1]);
        if (m_pLog != nullptr)
        {
            LOGV_WARN(*m_pLog, TF("CRedisWrapper command2 host=%s[%d] %s --- %s return false, disconnect"), *m_strAddr, m_nPort, pszArg[0], pszArg[1]);
        }
        Disconnect();
    }
    return (nRet == REDIS_OK);
}

INLINE Int CRedisWrapper::CommandInt(Int nArg, PCXStr* pszArg, size_t* pstArg)
{
    Int nRet = REDIS_ERR;
    redisReply* pReply = (redisReply*)redisCommandArgv(m_pContext, nArg, pszArg, pstArg);
    if (pReply != nullptr)
    {
        if (pReply->type == REDIS_REPLY_INTEGER)
        {
            nRet = (Int)pReply->integer;
        }
        else if (pReply->type != REDIS_REPLY_ERROR)
        {
            nRet = REDIS_OK;
        }
        else
        {
            DEV_WARN(TF("CRedisWrapper CommandInt1 return error=[%d]%s"), pReply->type, pReply->str);
            if (m_pLog != nullptr)
            {
                LOGV_WARN(*m_pLog, TF("CRedisWrapper CommandInt1 return error=[%d]%s"), pReply->type, pReply->str);
            }
        }
        freeReplyObject(pReply);
    }
    if (nRet == REDIS_ERR)
    {
        DEV_WARN(TF("CRedisWrapper commandint host=%s[%d] %s --- %s return false, disconnect"), *m_strAddr, m_nPort, pszArg[0], pszArg[1]);
        if (m_pLog != nullptr)
        {
            LOGV_WARN(*m_pLog, TF("CRedisWrapper commandint host=%s[%d] %s --- %s return false, disconnect"), *m_strAddr, m_nPort, pszArg[0], pszArg[1]);
        }
        Disconnect();
    }
    return nRet;
}

INLINE Int CRedisWrapper::CommandInt(CTArray<CString>& strMembers, Int nArg, PCXStr* pszArg, size_t* pstArg)
{
    Int nRet = REDIS_ERR;
    redisReply* pReply = (redisReply*)redisCommandArgv(m_pContext, nArg, pszArg, pstArg);
    if (pReply != nullptr)
    {
        if (pReply->type == REDIS_REPLY_ARRAY)
        {
            nRet = (Int)pReply->elements;
            for (Int i = 0; i < nRet; ++i)
            {
                if ((pReply->element[i]->type == REDIS_REPLY_STRING) &&
                    (pReply->element[i]->len > 0))
                {
                    strMembers.Add(pReply->element[i]->str);
                }
                else
                {
                    strMembers.Add(TF(""));
                }
            }
        }
        else if (pReply->type != REDIS_REPLY_ERROR)
        {
            nRet = REDIS_OK;
        }
        else
        {
            DEV_WARN(TF("CRedisWrapper CommandInt2 return error=[%d]%s"), pReply->type, pReply->str);
            if (m_pLog != nullptr)
            {
                LOGV_WARN(*m_pLog, TF("CRedisWrapper CommandInt2 return error=[%d]%s"), pReply->type, pReply->str);
            }
        }
        freeReplyObject(pReply);
    }
    if (nRet == REDIS_ERR)
    {
        DEV_WARN(TF("CRedisWrapper commandint2 host=%s[%d] %s --- %s return false, disconnect"), *m_strAddr, m_nPort, pszArg[0], pszArg[1]);
        if (m_pLog != nullptr)
        {
            LOGV_WARN(*m_pLog, TF("CRedisWrapper commandint2 host=%s[%d] %s --- %s return false, disconnect"), *m_strAddr, m_nPort, pszArg[0], pszArg[1]);
        }
        Disconnect();
    }
    return nRet;
}

INLINE Int CRedisWrapper::CommandInt(CTArray<CBufReadStream>& strMembers, Int nArg, PCXStr* pszArg, size_t* pstArg)
{
    Int nRet = REDIS_ERR;
    redisReply* pReply = (redisReply*)redisCommandArgv(m_pContext, nArg, pszArg, pstArg);
    if (pReply != nullptr)
    {
        if (pReply->type == REDIS_REPLY_ARRAY)
        {
            nRet = (Int)pReply->elements;
            if (nRet > 0)
            {
                strMembers.Add(nRet, false);
                for (Int i = 0; i < nRet; ++i)
                {
                    if ((pReply->element[i]->type == REDIS_REPLY_STRING) &&
                        (pReply->element[i]->len > 0) &&
                        (strMembers[i].Attach(pReply->element[i]->len)))
                    {
                        MM_SAFE::Cpy(strMembers[i].GetBuf(), strMembers[i].Size(),
                            pReply->str, pReply->element[i]->len);
                    }
                }
            }
        }
        else if (pReply->type != REDIS_REPLY_ERROR)
        {
            nRet = REDIS_OK;
        }
        else
        {
            DEV_WARN(TF("CRedisWrapper CommandInt3 return error=[%d]%s"), pReply->type, pReply->str);
            if (m_pLog != nullptr)
            {
                LOGV_WARN(*m_pLog, TF("CRedisWrapper CommandInt3 return error=[%d]%s"), pReply->type, pReply->str);
            }
        }
        freeReplyObject(pReply);
    }
    if (nRet == REDIS_ERR)
    {
        DEV_WARN(TF("CRedisWrapper commandint3 host=%s[%d] %s --- %s return false, disconnect"), *m_strAddr, m_nPort, pszArg[0], pszArg[1]);
        if (m_pLog != nullptr)
        {
            LOGV_WARN(*m_pLog, TF("CRedisWrapper commandint3 host=%s[%d] %s --- %s return false, disconnect"), *m_strAddr, m_nPort, pszArg[0], pszArg[1]);
        }
        Disconnect();
    }
    return nRet;
}

INLINE bool CRedisWrapper::AppendCommand(Int nArg, PCXStr* pszArg, size_t* pstArg)
{
    if (redisAppendCommandArgv(m_pContext, nArg, pszArg, pstArg) == REDIS_OK)
    {
        ++m_nPipeCmd;
        return true;
    }
    DEV_WARN(TF("CRedisWrapper appendcommand host=%s[%d] %s --- %s return false, disconnect"), *m_strAddr, m_nPort, pszArg[0], pszArg[1]);
    if (m_pLog != nullptr)
    {
        LOGV_WARN(*m_pLog, TF("CRedisWrapper appendcommand host=%s[%d] %s --- %s return false, disconnect"), *m_strAddr, m_nPort, pszArg[0], pszArg[1]);
    }
    Disconnect();
    return false;
}

#endif // USE_HIREDIS_LIB

#endif // __REDIS_WRAPPER_INL__
